/***********************************************************************

	This file is part of KEEL-software, the Data Mining tool for regression, 
	classification, clustering, pattern mining and so on.

	Copyright (C) 2004-2010
	
	F. Herrera (herrera@decsai.ugr.es)
    L. Sánchez (luciano@uniovi.es)
    J. Alcalá-Fdez (jalcala@decsai.ugr.es)
    S. García (sglopez@ujaen.es)
    A. Fernández (alberto.fernandez@ujaen.es)
    J. Luengo (julianlm@decsai.ugr.es)

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see http://www.gnu.org/licenses/
  
**********************************************************************/

/**
* <p>
* @author Written by Cristobal Romero (Universidad de Córdoba) 10/10/2007
* @version 0.1
* @since JDK 1.5
*</p>
*/

package keel.Algorithms.Decision_Trees.M5;

import java.util.*;
import java.io.*;

/**
 * Class for storing an instance as a sparse vector. A sparse instance
 * only requires storage for those attribute values that are non-zero.
 * Since the objective is to reduce storage requirements for datasets
 * with large numbers of default values, this also includes nominal
 * attributes -- the first nominal value (i.e. that which has index 0)
 * will not require explicit storage, so rearrange your nominal attribute
 * value orderings if necessary. Missing values will be stored
 * explicitly.
 */
public class M5SparseInstance extends M5Instance {

    /** The index of the attribute associated with each stored value. */
    protected int[] m_Indices;

    /** The maximum number of values that can be stored. */
    protected int m_NumAttributes;

    /**
     * Default Constructor.
     */
    protected M5SparseInstance() {

    }

    /**
     * Constructor that generates a sparse instance from the given
     * instance. Reference to the dataset is set to null.
     * (ie. the instance doesn't have access to information about the
     * attribute types)
     *
     * @param instance the instance from which the attribute values
     * and the weight are to be copied
     */
    public M5SparseInstance(M5Instance instance) {

        m_Weight = instance.m_Weight;
        m_Dataset = null;
        m_NumAttributes = instance.numAttributes();
        if (instance instanceof M5SparseInstance) {
            m_AttValues = ((M5SparseInstance) instance).m_AttValues;
            m_Indices = ((M5SparseInstance) instance).m_Indices;
        } else {
            double[] tempValues = new double[instance.numAttributes()];
            int[] tempIndices = new int[instance.numAttributes()];
            int vals = 0;
            for (int i = 0; i < instance.numAttributes(); i++) {
                if (instance.value(i) != 0) {
                    tempValues[vals] = instance.value(i);
                    tempIndices[vals] = i;
                    vals++;
                }
            }
            m_AttValues = new double[vals];
            m_Indices = new int[vals];
            System.arraycopy(tempValues, 0, m_AttValues, 0, vals);
            System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
        }
    }

    /**
     * Constructor that copies the info from the given instance.
     * Reference to the dataset is set to null.
     * (ie. the instance doesn't have access to information about the
     * attribute types)
     *
     * @param instance the instance from which the attribute
     * info is to be copied
     */
    public M5SparseInstance(M5SparseInstance instance) {

        m_AttValues = instance.m_AttValues;
        m_Indices = instance.m_Indices;
        m_Weight = instance.m_Weight;
        m_NumAttributes = instance.m_NumAttributes;
        m_Dataset = null;
    }

    /**
     * Constructor that generates a sparse instance from the given
     * parameters. Reference to the dataset is set to null.
     * (ie. the instance doesn't have access to information about the
     * attribute types)
     *
     * @param weight the instance's weight
     * @param attValues a vector of attribute values
     */
    public M5SparseInstance(double weight, double[] attValues) {

        m_Weight = weight;
        m_Dataset = null;
        m_NumAttributes = attValues.length;
        double[] tempValues = new double[m_NumAttributes];
        int[] tempIndices = new int[m_NumAttributes];
        int vals = 0;
        for (int i = 0; i < m_NumAttributes; i++) {
            if (attValues[i] != 0) {
                tempValues[vals] = attValues[i];
                tempIndices[vals] = i;
                vals++;
            }
        }
        m_AttValues = new double[vals];
        m_Indices = new int[vals];
        System.arraycopy(tempValues, 0, m_AttValues, 0, vals);
        System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
    }

    /**
     * Constructor that inititalizes instance variable with given
     * values. Reference to the dataset is set to null. (ie. the instance
     * doesn't have access to information about the attribute types)
     *
     * @param weight the instance's weight
     * @param attValues a vector of attribute values (just the ones to be stored)
     * @param indices the indices of the given values in the full vector
     * @param maxNumValues the maximium number of values that can be stored
     */
    public M5SparseInstance(double weight, double[] attValues,
                            int[] indices, int maxNumValues) {

        int vals = 0;
        m_AttValues = new double[attValues.length];
        m_Indices = new int[indices.length];
        for (int i = 0; i < attValues.length; i++) {
            if (attValues[i] != 0) {
                m_AttValues[vals] = attValues[i];
                m_Indices[vals] = indices[i];
                vals++;
            }
        }
        if (vals != attValues.length) {
            // Need to truncate.
            double[] newVals = new double[vals];
            System.arraycopy(m_AttValues, 0, newVals, 0, vals);
            m_AttValues = newVals;
            int[] newIndices = new int[vals];
            System.arraycopy(m_Indices, 0, newIndices, 0, vals);
            m_Indices = newIndices;
        }
        m_Weight = weight;
        m_NumAttributes = maxNumValues;
        m_Dataset = null;
    }

    /**
     * Constructor of an instance that sets weight to one, all values to
     * be missing, and the reference to the dataset to null. (ie. the instance
     * doesn't have access to information about the attribute types)
     *
     * @param numAttributes the size of the instance
     */
    public M5SparseInstance(int numAttributes) {

        m_AttValues = new double[numAttributes];
        m_NumAttributes = numAttributes;
        m_Indices = new int[numAttributes];
        for (int i = 0; i < m_AttValues.length; i++) {
            m_AttValues[i] = MISSING_VALUE;
            m_Indices[i] = i;
        }
        m_Weight = 1;
        m_Dataset = null;
    }

    /**
     * Returns the attribute associated with the internal index.
     *
     * @param indexOfIndex the index of the attribute's index
     * @return the attribute at the given position
     * @throws Exception if instance doesn't have access to a
     * dataset
     */
    public M5Attribute attributeSparse(int indexOfIndex) throws Exception {

        if (m_Dataset == null) {
            throw new Exception("Instance doesn't have access to a dataset!");
        }
        return m_Dataset.attribute(m_Indices[indexOfIndex]);
    }

    /**
     * Produces a shallow copy of this instance. The copy has
     * access to the same dataset. (if you want to make a copy
     * that doesn't have access to the dataset, use
     * <code>new M5SparseInstance(instance)</code>
     *
     * @return the shallow copy
     */
    public Object copy() {

        M5Instance result = new M5SparseInstance(this);
        result.m_Dataset = m_Dataset;
        return result;
    }

    /**
     * Returns the index of the attribute stored at the given position.
     *
     * @param position the position
     * @return the index of the attribute stored at the given position
     */
    public int index(int position) {

        return m_Indices[position];
    }

    /**
     * Tests if a specific value is "missing".
     *
     * @param attIndex the attribute's index
     * @return True if is missing, fals otherwise.
     */
    public boolean isMissing(int attIndex) {

        if (Double.isNaN(value(attIndex))) {
            return true;
        }
        return false;
    }

    /**
     * Locates the greatest index that is not greater than the
     * given index.
     *
     * @param index index given.
     * @return the internal index of the attribute index. Returns
     * -1 if no index with this property couldn't be found
     */
    public int locateIndex(int index) {

        int min = 0, max = m_Indices.length - 1;

        // Binary search
        while (max >= min) {
            int current = (max + min) / 2;
            if (m_Indices[current] > index) {
                max = current - 1;
            } else if (m_Indices[current] < index) {
                min = current + 1;
            } else {
                return current;
            }
        }
        return max;
    }

    /**
     * Merges this instance with the given instance and returns
     * the result. Dataset is set to null.
     *
     * @param inst the instance to be merged with this one
     * @return the merged instances
     */
    public M5Instance mergeInstance(M5Instance inst) {

        double[] values = new double[numValues() + inst.numValues()];
        int[] indices = new int[numValues() + inst.numValues()];

        int m = 0;
        for (int j = 0; j < numValues(); j++, m++) {
            values[m] = valueSparse(j);
            indices[m] = index(j);
        }
        for (int j = 0; j < inst.numValues(); j++, m++) {
            values[m] = inst.valueSparse(j);
            indices[m] = inst.index(j) + inst.numAttributes();
        }

        return new M5SparseInstance(1.0, values, indices, numAttributes() +
                                    inst.numAttributes());
    }

    /**
     * Returns the number of attributes.
     *
     * @return the number of attributes as an integer
     */
    public int numAttributes() {

        return m_NumAttributes;
    }

    /**
     * Returns the number of values in the sparse vector.
     *
     * @return the number of values
     */
    public int numValues() {

        return m_Indices.length;
    }

    /**
     * Replaces all missing values in the instance with the
     * values contained in the given array. A deep copy of
     * the vector of attribute values is performed before the
     * values are replaced.
     *
     * @param array containing the means and modes
     * @exception IllegalArgumentException if numbers of attributes are unequal
     */
    public void replaceMissingValues(double[] array) {

        if ((array == null) || (array.length != m_NumAttributes)) {
            throw new IllegalArgumentException("Unequal number of attributes!");
        }
        double[] tempValues = new double[m_AttValues.length];
        int[] tempIndices = new int[m_AttValues.length];
        int vals = 0;
        for (int i = 0; i < m_AttValues.length; i++) {
            if (isMissingValue(m_AttValues[i])) {
                if (array[m_Indices[i]] != 0) {
                    tempValues[vals] = array[m_Indices[i]];
                    tempIndices[vals] = m_Indices[i];
                    vals++;
                }
            } else {
                tempValues[vals] = m_AttValues[i];
                tempIndices[vals] = m_Indices[i];
                vals++;
            }
        }
        m_AttValues = new double[vals];
        m_Indices = new int[vals];
        System.arraycopy(tempValues, 0, m_AttValues, 0, vals);
        System.arraycopy(tempIndices, 0, m_Indices, 0, vals);
    }

    /**
     * Sets a specific value in the instance to the given value
     * (internal floating-point format). Performs a deep copy
     * of the vector of attribute values before the value is set.
     *
     * @param attIndex the attribute's index
     * @param value the new attribute value (If the corresponding
     * attribute is nominal (or a string) then this is the new value's
     * index as a double).
     */
    public void setValue(int attIndex, double value) {

        int index = locateIndex(attIndex);

        if ((index >= 0) && (m_Indices[index] == attIndex)) {
            if (value != 0) {
                double[] tempValues = new double[m_AttValues.length];
                System.arraycopy(m_AttValues, 0, tempValues, 0,
                                 m_AttValues.length);
                tempValues[index] = value;
                m_AttValues = tempValues;
            } else {
                double[] tempValues = new double[m_AttValues.length - 1];
                int[] tempIndices = new int[m_Indices.length - 1];
                System.arraycopy(m_AttValues, 0, tempValues, 0, index);
                System.arraycopy(m_Indices, 0, tempIndices, 0, index);
                System.arraycopy(m_AttValues, index + 1, tempValues, index,
                                 m_AttValues.length - index - 1);
                System.arraycopy(m_Indices, index + 1, tempIndices, index,
                                 m_Indices.length - index - 1);
                m_AttValues = tempValues;
                m_Indices = tempIndices;
            }
        } else {
            if (value != 0) {
                double[] tempValues = new double[m_AttValues.length + 1];
                int[] tempIndices = new int[m_Indices.length + 1];
                System.arraycopy(m_AttValues, 0, tempValues, 0, index + 1);
                System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
                tempIndices[index + 1] = attIndex;
                tempValues[index + 1] = value;
                System.arraycopy(m_AttValues, index + 1, tempValues, index + 2,
                                 m_AttValues.length - index - 1);
                System.arraycopy(m_Indices, index + 1, tempIndices, index + 2,
                                 m_Indices.length - index - 1);
                m_AttValues = tempValues;
                m_Indices = tempIndices;
            }
        }
    }

    /**
     * Sets a specific value in the instance to the given value
     * (internal floating-point format). Performs a deep copy
     * of the vector of attribute values before the value is set.
     *
     * @param indexOfIndex the index of the attribute's index
     * @param value the new attribute value (If the corresponding
     * attribute is nominal (or a string) then this is the new value's
     * index as a double).
     */
    public void setValueSparse(int indexOfIndex, double value) {

        if (value != 0) {
            double[] tempValues = new double[m_AttValues.length];
            System.arraycopy(m_AttValues, 0, tempValues, 0, m_AttValues.length);
            m_AttValues = tempValues;
            m_AttValues[indexOfIndex] = value;
        } else {
            double[] tempValues = new double[m_AttValues.length - 1];
            int[] tempIndices = new int[m_Indices.length - 1];
            System.arraycopy(m_AttValues, 0, tempValues, 0, indexOfIndex);
            System.arraycopy(m_Indices, 0, tempIndices, 0, indexOfIndex);
            System.arraycopy(m_AttValues, indexOfIndex + 1, tempValues,
                             indexOfIndex,
                             m_AttValues.length - indexOfIndex - 1);
            System.arraycopy(m_Indices, indexOfIndex + 1, tempIndices,
                             indexOfIndex,
                             m_Indices.length - indexOfIndex - 1);
            m_AttValues = tempValues;
            m_Indices = tempIndices;
        }
    }

    /**
     * Returns the values of each attribute as an array of doubles.
     *
     * @return an array containing all the instance attribute values
     */
    public double[] toDoubleArray() {

        double[] newValues = new double[m_NumAttributes];
        for (int i = 0; i < m_AttValues.length; i++) {
            newValues[m_Indices[i]] = m_AttValues[i];
        }
        return newValues;
    }

    /**
     * Returns the description of one instance in sparse format.
     * If the instance doesn't have access to a dataset, it returns the
     * internal floating-point values. Quotes string values that contain
     * whitespace characters.
     *
     * @return the instance's description as a string
     */
    public String toString() {

        StringBuffer text = new StringBuffer();

        text.append('{');
        for (int i = 0; i < m_Indices.length; i++) {
            if (i > 0) {
                text.append(",");
            }
            if (isMissingValue(m_AttValues[i])) {
                text.append(m_Indices[i] + " ?");
            } else {
                if (m_Dataset == null) {
                    text.append(m_Indices[i] + " " +
                                M5StaticUtils.doubleToString(m_AttValues[i], 6));
                } else {
                    if (m_Dataset.attribute(m_Indices[i]).isNominal() ||
                        m_Dataset.attribute(m_Indices[i]).isString()) {
                        try {
                            text.append(m_Indices[i] + " " +
                                        M5StaticUtils.quote(m_Dataset.attribute(
                                    m_Indices[i]).
                                    value((int) valueSparse(i))));
                        } catch (Exception e) {
                            e.printStackTrace();
                            System.err.println(new M5Instances(m_Dataset, 0));
                            System.err.println("Att:" + m_Indices[i] + " Val:" +
                                               valueSparse(i));
                            throw new Error("This should never happen!");
                        }
                    } else {
                        text.append(m_Indices[i] + " " +
                                    M5StaticUtils.doubleToString(m_AttValues[i],
                                6));
                    }
                }
            }
        }
        text.append('}');
        return text.toString();
    }

    /**
     * Returns an instance's attribute value in internal format.
     *
     * @param attIndex the attribute's index
     * @return the specified value as a double (If the corresponding
     * attribute is nominal (or a string) then it returns the value's index as a
     * double).
     */
    public double value(int attIndex) {

        int index = locateIndex(attIndex);
        if ((index >= 0) && (m_Indices[index] == attIndex)) {
            return m_AttValues[index];
        } else {
            return 0.0;
        }
    }

    /**
     * Deletes an attribute at the given position (0 to
     * numAttributes() - 1).
     *
     * @param pos the attribute's position
     */
    void forceDeleteAttributeAt(int position) {

        int index = locateIndex(position);

        m_NumAttributes--;
        if ((index >= 0) && (m_Indices[index] == position)) {
            int[] tempIndices = new int[m_Indices.length - 1];
            double[] tempValues = new double[m_AttValues.length - 1];
            System.arraycopy(m_Indices, 0, tempIndices, 0, index);
            System.arraycopy(m_AttValues, 0, tempValues, 0, index);
            for (int i = index; i < m_Indices.length - 1; i++) {
                tempIndices[i] = m_Indices[i + 1] - 1;
                tempValues[i] = m_AttValues[i + 1];
            }
            m_Indices = tempIndices;
            m_AttValues = tempValues;
        } else {
            int[] tempIndices = new int[m_Indices.length];
            double[] tempValues = new double[m_AttValues.length];
            System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
            System.arraycopy(m_AttValues, 0, tempValues, 0, index + 1);
            for (int i = index + 1; i < m_Indices.length - 1; i++) {
                tempIndices[i] = m_Indices[i] - 1;
                tempValues[i] = m_AttValues[i];
            }
            m_Indices = tempIndices;
            m_AttValues = tempValues;
        }
    }

    /**
     * Inserts an attribute at the given position
     * (0 to numAttributes()) and sets its value to be missing.
     *
     * @param pos the attribute's position
     */
    void forceInsertAttributeAt(int position) {

        int index = locateIndex(position);

        m_NumAttributes++;
        if ((index >= 0) && (m_Indices[index] == position)) {
            int[] tempIndices = new int[m_Indices.length + 1];
            double[] tempValues = new double[m_AttValues.length + 1];
            System.arraycopy(m_Indices, 0, tempIndices, 0, index);
            System.arraycopy(m_AttValues, 0, tempValues, 0, index);
            tempIndices[index] = position;
            tempValues[index] = MISSING_VALUE;
            for (int i = index; i < m_Indices.length; i++) {
                tempIndices[i + 1] = m_Indices[i] + 1;
                tempValues[i + 1] = m_AttValues[i];
            }
            m_Indices = tempIndices;
            m_AttValues = tempValues;
        } else {
            int[] tempIndices = new int[m_Indices.length + 1];
            double[] tempValues = new double[m_AttValues.length + 1];
            System.arraycopy(m_Indices, 0, tempIndices, 0, index + 1);
            System.arraycopy(m_AttValues, 0, tempValues, 0, index + 1);
            tempIndices[index + 1] = position;
            tempValues[index + 1] = MISSING_VALUE;
            for (int i = index + 1; i < m_Indices.length; i++) {
                tempIndices[i + 1] = m_Indices[i] + 1;
                tempValues[i + 1] = m_AttValues[i];
            }
            m_Indices = tempIndices;
            m_AttValues = tempValues;
        }
    }

    /**
     * Main method for testing this class.
     * @param options main args.
     */
    public static void main(String[] options) {

        try {

            // Create numeric attributes "length" and "weight"
            M5Attribute length = new M5Attribute("length");
            M5Attribute weight = new M5Attribute("weight");

            // Create vector to hold nominal values "first", "second", "third"
            M5Vector my_nominal_values = new M5Vector(3);
            my_nominal_values.addElement("first");
            my_nominal_values.addElement("second");
            my_nominal_values.addElement("third");

            // Create nominal attribute "position"
            M5Attribute position = new M5Attribute("position",
                    my_nominal_values);

            // Create vector of the above attributes
            M5Vector attributes = new M5Vector(3);
            attributes.addElement(length);
            attributes.addElement(weight);
            attributes.addElement(position);

            // Create the empty dataset "race" with above attributes
            M5Instances race = new M5Instances("race", attributes, 0);

            // Make position the class attribute
            race.setClassIndex(position.index());

            // Create empty instance with three attribute values
            M5SparseInstance inst = new M5SparseInstance(3);

            // Set instance's values for the attributes "length", "weight", and "position"
            inst.setValue(length, 5.3);
            inst.setValue(weight, 300);
            inst.setValue(position, "first");

            // Set instance's dataset to be the dataset "race"
            inst.setDataset(race);

            // Print the instance
            System.out.println("The instance: " + inst);

            // Print the first attribute
            System.out.println("First attribute: " + inst.attribute(0));

            // Print the class attribute
            System.out.println("Class attribute: " + inst.classAttribute());

            // Print the class index
            System.out.println("Class index: " + inst.classIndex());

            // Say if class is missing
            System.out.println("Class is missing: " + inst.classIsMissing());

            // Print the instance's class value in internal format
            System.out.println("Class value (internal format): " +
                               inst.classValue());

            // Print a shallow copy of this instance
            M5SparseInstance copy = (M5SparseInstance) inst.copy();
            System.out.println("Shallow copy: " + copy);

            // Set dataset for shallow copy
            copy.setDataset(inst.dataset());
            System.out.println("Shallow copy with dataset set: " + copy);

            // Print out all values in internal format
            System.out.print("All stored values in internal format: ");
            for (int i = 0; i < inst.numValues(); i++) {
                if (i > 0) {
                    System.out.print(",");
                }
                System.out.print(inst.valueSparse(i));
            }
            System.out.println();

            // Set all values to zero
            System.out.print("All values set to zero: ");
            while (inst.numValues() > 0) {
                inst.setValueSparse(0, 0);
            }
            for (int i = 0; i < inst.numValues(); i++) {
                if (i > 0) {
                    System.out.print(",");
                }
                System.out.print(inst.valueSparse(i));
            }
            System.out.println();

            // Set all values to one
            System.out.print("All values set to one: ");
            for (int i = 0; i < inst.numAttributes(); i++) {
                inst.setValue(i, 1);
            }
            for (int i = 0; i < inst.numValues(); i++) {
                if (i > 0) {
                    System.out.print(",");
                }
                System.out.print(inst.valueSparse(i));
            }
            System.out.println();

            // Unset dataset for copy, delete first attribute, and insert it again
            copy.setDataset(null);
            copy.deleteAttributeAt(0);
            copy.insertAttributeAt(0);
            copy.setDataset(inst.dataset());
            System.out.println(
                    "Copy with first attribute deleted and inserted: " + copy);

            // Same for second attribute
            copy.setDataset(null);
            copy.deleteAttributeAt(1);
            copy.insertAttributeAt(1);
            copy.setDataset(inst.dataset());
            System.out.println(
                    "Copy with second attribute deleted and inserted: " + copy);

            // Same for last attribute
            copy.setDataset(null);
            copy.deleteAttributeAt(2);
            copy.insertAttributeAt(2);
            copy.setDataset(inst.dataset());
            System.out.println(
                    "Copy with third attribute deleted and inserted: " + copy);

            // Enumerate attributes (leaving out the class attribute)
            System.out.println("Enumerating attributes (leaving out class):");
            Enumeration enuma = inst.enumerateAttributes();
            while (enuma.hasMoreElements()) {
                M5Attribute att = (M5Attribute) enuma.nextElement();
                System.out.println(att);
            }

            // Headers are equivalent?
            System.out.println("Header of original and copy equivalent: " +
                               inst.equalHeaders(copy));

            // Test for missing values
            System.out.println("Length of copy missing: " +
                               copy.isMissing(length));
            System.out.println("Weight of copy missing: " +
                               copy.isMissing(weight.index()));
            System.out.println("Length of copy missing: " +
                               M5Instance.isMissingValue(copy.value(length)));
            System.out.println("Missing value coded as: " +
                               M5Instance.missingValue());

            // Prints number of attributes and classes
            System.out.println("Number of attributes: " + copy.numAttributes());
            System.out.println("Number of classes: " + copy.numClasses());

            // Replace missing values
            double[] meansAndModes = {2, 3, 0};
            copy.replaceMissingValues(meansAndModes);
            System.out.println("Copy with missing value replaced: " + copy);

            // Setting and getting values and weights
            copy.setClassMissing();
            System.out.println("Copy with missing class: " + copy);
            copy.setClassValue(0);
            System.out.println("Copy with class value set to first value: " +
                               copy);
            copy.setClassValue("third");
            System.out.println("Copy with class value set to \"third\": " +
                               copy);
            copy.setMissing(1);
            System.out.println("Copy with second attribute set to be missing: " +
                               copy);
            copy.setMissing(length);
            System.out.println("Copy with length set to be missing: " + copy);
            copy.setValue(0, 0);
            System.out.println("Copy with first attribute set to 0: " + copy);
            copy.setValue(weight, 1);
            System.out.println("Copy with weight attribute set to 1: " + copy);
            copy.setValue(position, "second");
            System.out.println("Copy with position set to \"second\": " + copy);
            copy.setValue(2, "first");
            System.out.println("Copy with last attribute set to \"first\": " +
                               copy);
            System.out.println("Current weight of instance copy: " +
                               copy.weight());
            copy.setWeight(2);
            System.out.println("Current weight of instance copy (set to 2): " +
                               copy.weight());
            System.out.println("Last value of copy: " + copy.toString(2));
            System.out.println("Value of position for copy: " +
                               copy.toString(position));
            System.out.println("Last value of copy (internal format): " +
                               copy.value(2));
            System.out.println("Value of position for copy (internal format): " +
                               copy.value(position));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


